﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using SharpSvn;

namespace SvnFlow
{
    public class FlowSvnClient
    {
        private readonly SvnClient m_SvnClient;
        private List<MergeConflict> m_Conflicts;

        public FlowSvnClient(SvnClient svnClient)
        {
            m_SvnClient = svnClient;
        }

        public List<SvnLogMessage> BuildLogMessage(BranchInfo branch, SvnRevisionRange range, string folder)
        {
            if (branch == null) throw new ArgumentNullException("branch");
            if (range == null) throw new ArgumentNullException("range");
            if (folder == null) throw new ArgumentNullException("folder");

            Logger.WriteLine("Building log message...");

            var sb = new List<SvnLogMessage>();

            var uri = new Uri(branch.Uri, folder);

            m_SvnClient.Log(
                uri,
                new SvnLogArgs { Range = range },
                (o, e) => sb.Add(
                    new SvnLogMessage { Revision = e.Revision, Author = e.Author, Time = e.Time, Message = e.LogMessage }
                ));

            Logger.WriteLine("Building log message is complete");

            return sb;
        }

        public List<MergeConflict> MergeChanges(
            BranchInfo fromBranch, string fromFolder,
            BranchInfo toBranch, string toFolder,
            SvnRevisionRange range)
        {
            if (fromBranch == null) throw new ArgumentNullException("fromBranch");
            if (fromFolder == null) throw new ArgumentNullException("fromFolder");
            if (toBranch == null) throw new ArgumentNullException("fromBranch");
            if (toFolder == null) throw new ArgumentNullException("fromFolder");
            if (range == null) throw new ArgumentNullException("range");

            m_Conflicts = new List<MergeConflict>();

            Logger.WriteLine(
                "Merging changes ({0}-{1})...", fromBranch.Range.StartRevision, fromBranch.Range.EndRevision);

            var mergeArgs = new SvnMergeArgs { IgnoreAncestry = true, };
            mergeArgs.Conflict += (o, e) => m_Conflicts.Add(new EditConflict(e.Path));
            mergeArgs.Notify += (o, e) =>
                        {
                            if (e.Action == SvnNotifyAction.TreeConflict)
                                m_Conflicts.Add(new TreeConflict(e.Path));
                        };
            var targetPath = Path.Combine(toBranch.FullPath, toFolder);
            var source = new SvnUriTarget(new Uri(fromBranch.Uri, fromFolder));
            m_SvnClient.Merge(
                targetPath,
                source,
                range, mergeArgs);

            if (m_Conflicts.Count > 0)
            {
                Logger.WriteLine("Found {0} conflict{1}", m_Conflicts.Count, m_Conflicts.Count > 1 ? "s" : "");
                Logger.WriteLine(new string('-', 50));
                foreach (var conflict in m_Conflicts)
                {
                    Logger.WriteLine("   {0}", conflict.ToString());
                }
            }
            else
            {
                Logger.WriteLine("Merging is complete");
            }
            return m_Conflicts;
        }

        public void FixSpecialProperties(BranchInfo branch, string folder, string[] allowProperties)
        {
            if (branch == null) throw new ArgumentNullException("branch");
            if (folder == null) throw new ArgumentNullException("folder");
            if (allowProperties == null) throw new ArgumentNullException("allowProperties");

            Logger.WriteLine("Fixing special properties...");

            var changes = new List<string>();
            m_SvnClient.ListChangeList(Path.Combine(branch.FullPath, folder), (o, e) => changes.Add(e.Path));

            var statuses = new List<SvnStatusEventArgs>();
            foreach (var change in changes)
            {
                m_SvnClient.Status(change,
                                 (o, e) =>
                                 {
                                     if (e.WorkingCopyInfo != null && e.WorkingCopyInfo.HasPropertyChanges)
                                     {
                                         e.Detach();
                                         statuses.Add(e);
                                     }
                                 });
            }

            foreach (var status in statuses)
            {
                Collection<SvnPropertyListEventArgs> baseProps;
                m_SvnClient.GetPropertyList(new SvnPathTarget(status.FullPath, SvnRevisionType.Base), out baseProps);

                Collection<SvnPropertyListEventArgs> localProps;
                m_SvnClient.GetPropertyList(new SvnPathTarget(status.FullPath), out localProps);

                if (baseProps.Count == 0)
                    continue;

                var propertiesForCheck = from property in baseProps[0].Properties
                                         where !allowProperties.Contains(property.Key)
                                         select property;
                foreach (var svnProperty in propertiesForCheck)
                {
                    FixProperty(svnProperty, status);
                }
            }

            Logger.WriteLine("Special properties are fixed");
        }

        private void FixProperty(SvnPropertyValue prop, SvnStatusEventArgs status)
        {
            m_SvnClient.SetProperty(status.FullPath, prop.Key, prop.StringValue);
        }

        public void MarkPropertyConflictsResolved(BranchInfo branch, string folder)
        {
            if (branch == null) throw new ArgumentNullException("branch");
            if (folder == null) throw new ArgumentNullException("folder");

            Logger.WriteLine("Fixing conflicts...");

            var changes = new List<string>();
            m_SvnClient.ListChangeList(Path.Combine(branch.FullPath, folder), (o, e) => changes.Add(e.Path));

            var statuses = new List<SvnStatusEventArgs>();
            foreach (var change in changes)
            {
                m_SvnClient.Status(change, (o, e) =>
                                        {
                                            if (e.LocalPropertyStatus == SvnStatus.Conflicted
                                                && e.LocalContentStatus == SvnStatus.Normal
                                                && e.WorkingCopyInfo != null
                                                && !e.WorkingCopyInfo.HasPropertyChanges)
                                            {
                                                e.Detach();
                                                statuses.Add(e);
                                            }
                                        });
            }

            foreach (var status in statuses)
            {
                m_SvnClient.Resolved(status.FullPath);
            }

            Logger.WriteLine("Conflicts are fixed");
        }

        public void PrepareWorkingCopy(BranchInfo branch)
        {
            Logger.WriteLine(" From URL: {0}", branch.Uri);
            Logger.WriteLine("To branch: {0}", branch.FullPath);

            if (Directory.Exists(branch.FullPath) && Directory.GetFileSystemEntries(branch.FullPath).Length > 0)
            {
                Logger.WriteLine("Working Directory exists and is not empty");
                Logger.WriteLine("Checking local status...");

                Collection<SvnStatusEventArgs> statuses;
                m_SvnClient.GetStatus(branch.FullPath, out statuses);
                statuses.ToList().ForEach(x => x.Detach());

                if (statuses.Count > 0)
                {
                    if (statuses.Any(x => x.WorkingCopyInfo == null))
                    {
                        Logger.WriteLine("Working Directory is invalid. Deleting...");
                        SvnHelper.DeleteRecursiveFolder(branch.FullPath);
                    }
                    else if (statuses.Any(x => x.Wedged))
                    {
                        Logger.WriteLine("Working Directory is locked. Deleting...");
                        SvnHelper.DeleteRecursiveFolder(branch.FullPath);
                    }
                    else
                    {
                        Logger.WriteLine("There are local changes found");
                        Logger.WriteLine("Calling Revert...");
                        m_SvnClient.Revert(branch.FullPath, new SvnRevertArgs { Depth = SvnDepth.Infinity });
                    }
                }
                else
                {
                    Logger.WriteLine("No local changes");
                }
            }

            if (!Directory.Exists(branch.FullPath))
            {
                Logger.WriteLine("Working Directory is missing. Creating...");
                Directory.CreateDirectory(branch.FullPath);
            }

            if (Directory.GetDirectories(branch.FullPath).Length == 0)
            {
                Logger.WriteLine("Working Directory is empty");
                Logger.WriteLine("Calling CheckOut...");
                m_SvnClient.CheckOut(new SvnUriTarget(branch.Uri), branch.FullPath);
            }
            else
            {
                SvnUpdateResult result;
                Logger.WriteLine("Calling Update...");
                m_SvnClient.Update(branch.FullPath, new SvnUpdateArgs(), out result);
            }

            Logger.WriteLine("Done!                       ");
        }

        public long GetWorkingCopyRevision(BranchInfo branch)
        {
            SvnInfoEventArgs infoEventArgs;
            m_SvnClient.GetInfo(new SvnPathTarget(branch.FullPath), out infoEventArgs);

            Logger.WriteLine("Revision {0}", infoEventArgs.Revision);
            Logger.WriteLine(new string('-', 50));

            return infoEventArgs.Revision;
        }

    }
}
